/******************************************************************************
* Copyright (C) Ultraleap, Inc. 2011-2021. *
* *
* Use subject to the terms of the Apache License 2.0 available at *
* http://www.apache.org/licenses/LICENSE-2.0, or another agreement *
* between Ultraleap and you, your company or other organization. *
******************************************************************************/
using Leap.Interaction.Internal.InteractionEngineUtility;
using Leap.Unity.Attributes;
using Leap.Unity.Interaction.Internal;
using Leap.Unity.Query;
using Leap.Unity.RuntimeGizmos;
using Leap.Unity.Space;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Leap.Unity.Interaction
{
public enum HandDataMode { PlayerLeft, PlayerRight, Custom }
[DisallowMultipleComponent]
public class InteractionHand : InteractionController
{
#region Inspector
[SerializeField]
private LeapProvider _leapProvider;
[Header("Hand Configuration")]
[Tooltip("Should the data for the underlying Leap hand come from the player's left "
+ "hand or their right hand? Alternatively, you can set this mode to Custom "
+ "to specify accessor functions manually via script (recommended for advanced "
+ "users only).")]
[SerializeField, EditTimeOnly]
private HandDataMode _handDataMode;
public HandDataMode handDataMode
{
get { return _handDataMode; }
set
{
// TODO: Do validation if this is modified!
_handDataMode = value;
}
}
private bool _indexOnly = false;
public bool OnlyInitialiseIndexFinger
{
get { return _indexOnly; }
set
{
_indexOnly = value;
initContact();
}
}
///
/// Set slots to true to consider the corresponding finger's fingertip for primary
/// hover checks. 0 is the thumb, 1 is the index finger, etc. Generally speaking,
/// enable the fingertips you'd like users to be able to use to choose and push a
/// button, but keep in mind you pay distance check costs for each fingertip enabled!
///
public bool[] enabledPrimaryHoverFingertips = new bool[5] { true, true, true, false, false };
#endregion
#region Hand Data
///
/// If the hand data mode for this InteractionHand is set to Custom, you must also
/// manually specify the provider from which to retrieve Leap frames containing
/// hand data.
///
public LeapProvider leapProvider
{
get { return _leapProvider; }
set
{
if (_leapProvider != null && Application.isPlaying)
{
_leapProvider.OnFixedFrame -= onProviderFixedFrame;
}
_leapProvider = value;
if (_leapProvider != null && Application.isPlaying)
{
_leapProvider.OnFixedFrame += onProviderFixedFrame;
}
}
}
private Func _handAccessorFunc;
///
/// If the hand data mode for this InteractionHand is set to Custom, you must
/// manually specify how this InteractionHand should retrieve a specific Hand data
/// object from a Leap frame.
///
public Func handAccessorFunc
{
get { return _handAccessorFunc; }
set { _handAccessorFunc = value; }
}
///
/// A copy of the latest tracked hand data; never null, never warped.
///
private Hand _handData = new Hand();
///
/// An unwarped copy of _handData (if unwarping is necessary; otherwise
/// identical). Only relevant when using the Leap Graphical Renderer to create
/// curved user interfaces.
///
private Hand _unwarpedHandData = new Hand();
///
/// Will be null when not tracked, otherwise contains the same data as _handData.
///
private Hand _hand;
public Transform headTransform;
#endregion
#region Unity Events
protected override void Reset()
{
base.Reset();
enabledPrimaryHoverFingertips = new bool[] { true, true, true, false, false };
}
protected override void Start()
{
base.Start();
// Check manual configuration if data mode is custom.
if (handDataMode == HandDataMode.Custom)
{
if (leapProvider == null)
{
Debug.LogError("handDataMode is set to Custom, but no provider is set! "
+ "Please add a custom script that will configure the correct "
+ "LeapProvider for this InteractionHand before its Start() is "
+ "called, or set the handDataMode to a value other than Custom.",
this);
return;
}
else if (handAccessorFunc == null)
{
Debug.LogError("handDataMode is set to Custom, but no handAccessorFunc has "
+ "been set! Please add a custom script that will configure the "
+ "hand accessor function that will convert Leap frames into "
+ "Leap hand data for this InteractionHand before its Start() "
+ "is called, or set the handDataMode to a value other than "
+ "Custom.", this);
return;
}
}
else
{ // Otherwise, configure automatically.
if (leapProvider == null)
{
leapProvider = Hands.Provider;
if (leapProvider == null)
{
Debug.LogError("No LeapServiceProvider was found in your scene! Please "
+ "make sure you have a LeapServiceProvider if you intend to "
+ "use Leap hands in your scene.", this);
return;
}
}
if (handAccessorFunc == null)
{
if (handDataMode == HandDataMode.PlayerLeft)
{
handAccessorFunc = (frame) => frame.Hands.Query()
.FirstOrDefault(hand => hand.IsLeft);
}
else
{
handAccessorFunc = (frame) => frame.Hands.Query()
.FirstOrDefault(hand => hand.IsRight);
}
}
}
leapProvider.OnFixedFrame -= onProviderFixedFrame; // avoid double-subscribe
leapProvider.OnFixedFrame += onProviderFixedFrame;
// Set up hover point Transform for the palm.
Transform palmTransform = new GameObject("Palm Transform").transform;
palmTransform.parent = this.transform;
_backingHoverPointTransform = palmTransform;
// Set up primary hover point Transforms for the fingertips. We'll only use
// some of them, depending on user settings.
for (int i = 0; i < 5; i++)
{
Transform fingertipTransform = new GameObject("Fingertip Transform").transform;
fingertipTransform.parent = this.transform;
_backingFingertipTransforms.Add(fingertipTransform);
_fingertipTransforms.Add(null);
}
}
private void OnDestroy()
{
if (_leapProvider != null)
{
_leapProvider.OnFixedFrame -= onProviderFixedFrame;
}
}
private void onProviderFixedFrame(Leap.Frame frame)
{
_hand = handAccessorFunc(frame);
if (_hand != null)
{
_handData.CopyFrom(_hand);
_unwarpedHandData.CopyFrom(_handData);
refreshPointDataFromHand();
_lastCustomHandWasLeft = _unwarpedHandData.IsLeft;
}
}
#endregion
#region General InteractionController Implementation
///
/// Gets whether the underlying Leap hand is currently tracked.
///
public override bool isTracked { get { return _hand != null; } }
///
/// Gets whether the underlying Leap hand is currently being moved in worldspace.
///
public override bool isBeingMoved { get { return isTracked; } }
///
/// Gets the last tracked state of the Leap hand.
///
/// Note for those using the Leap Graphical Renderer: If the hand required warping
/// due to the nearby presence of an object in warped (curved) space, this will
/// return the hand as warped from that object's curved space into the rectilinear
/// space containing its colliders. This is only relevant if you are using the Leap
/// Graphical Renderer to render curved, interactive objects.
///
public Hand leapHand { get { return _unwarpedHandData; } }
private bool _lastCustomHandWasLeft = false;
///
/// Gets whether the underlying tracked Leap hand is a left hand.
///
public override bool isLeft
{
get
{
switch (handDataMode)
{
case HandDataMode.PlayerLeft:
return true;
case HandDataMode.PlayerRight:
return false;
case HandDataMode.Custom:
default:
return _lastCustomHandWasLeft;
}
}
}
///
/// Gets the last-tracked position of the underlying Leap hand.
///
public override Vector3 position
{
get { return _handData.PalmPosition.ToVector3(); }
}
///
/// Gets the last-tracked rotation of the underlying Leap hand.
///
public override Quaternion rotation
{
get { return _handData.Rotation.ToQuaternion(); }
}
///
/// Gets the velocity of the underlying tracked Leap hand.
///
public override Vector3 velocity
{
get { return isTracked ? leapHand.PalmVelocity.ToVector3() : Vector3.zero; }
}
///
/// Gets the controller type of this InteractionControllerBase. InteractionHands
/// are Interaction Engine controllers implemented over Leap hands.
///
public override ControllerType controllerType
{
get { return ControllerType.Hand; }
}
///
/// Returns this InteractionHand object. This property will be null if the
/// InteractionControllerBase is not ControllerType.Hand.
///
public override InteractionHand intHand
{
get { return this; }
}
protected override void onObjectUnregistered(IInteractionBehaviour intObj)
{
grabClassifier.UnregisterInteractionBehaviour(intObj);
}
protected override void fixedUpdateController()
{
// Transform the hand ahead if the manager is in a moving reference frame.
if (manager.hasMovingFrameOfReference)
{
Vector3 transformAheadPosition;
Quaternion transformAheadRotation;
manager.TransformAheadByFixedUpdate(_unwarpedHandData.PalmPosition.ToVector3(),
_unwarpedHandData.Rotation.ToQuaternion(),
out transformAheadPosition,
out transformAheadRotation);
_unwarpedHandData.SetTransform(transformAheadPosition, transformAheadRotation);
}
}
#endregion
#region Hovering Controller Implementation
private Transform _backingHoverPointTransform = null;
public override Vector3 hoverPoint
{
get
{
if (_backingHoverPointTransform == null)
{
return leapHand.PalmPosition.ToVector3();
}
else
{
return _backingHoverPointTransform.position;
}
}
}
private List _backingFingertipTransforms = new List();
private List _fingertipTransforms = new List();
protected override List _primaryHoverPoints
{
get
{
return _fingertipTransforms;
}
}
private void refreshPointDataFromHand()
{
refreshHoverPoint();
refreshPrimaryHoverPoints();
refreshGraspManipulatorPoints();
}
private void refreshHoverPoint()
{
_backingHoverPointTransform.position = leapHand.PalmPosition.ToVector3();
_backingHoverPointTransform.rotation = leapHand.Rotation.ToQuaternion();
}
private void refreshPrimaryHoverPoints()
{
for (int i = 0; i < enabledPrimaryHoverFingertips.Length; i++)
{
if (enabledPrimaryHoverFingertips[i])
{
_fingertipTransforms[i] = _backingFingertipTransforms[i];
Finger finger = leapHand.Fingers[i];
_fingertipTransforms[i].position = finger.TipPosition.ToVector3();
_fingertipTransforms[i].rotation = finger.bones[3].Rotation.ToQuaternion();
}
else
{
_fingertipTransforms[i] = null;
}
}
}
protected override void unwarpColliders(Transform primaryHoverPoint,
ISpaceComponent warpedSpaceElement)
{
// Extension method calculates "unwarped" pose in world space.
Vector3 unwarpedPosition;
Quaternion unwarpedRotation;
warpedSpaceElement.anchor.transformer.WorldSpaceUnwarp(primaryHoverPoint.position,
primaryHoverPoint.rotation,
out unwarpedPosition,
out unwarpedRotation);
// First shift the hand to be centered on the fingertip position so that rotations
// applied to the hand will pivot around the fingertip, then apply the rest of the
// transformation.
_unwarpedHandData.Transform(-primaryHoverPoint.position, Quaternion.identity);
_unwarpedHandData.Transform(unwarpedPosition, unwarpedRotation
* Quaternion.Inverse(primaryHoverPoint.rotation));
// Hand data was modified, so refresh point data.
refreshPointDataFromHand();
}
#endregion
#region Contact Controller Implementation
private const int NUM_FINGERS = 5;
private const int BONES_PER_FINGER = 3;
private ContactBone[] _contactBones;
public override ContactBone[] contactBones
{
get { return _contactBones; }
}
private GameObject _contactBoneParent;
protected override GameObject contactBoneParent
{
get { return _contactBoneParent; }
}
private delegate void BoneMapFunc(Leap.Hand hand, out Vector3 targetPosition,
out Quaternion targetRotation);
private BoneMapFunc[] _handContactBoneMapFunctions;
protected override void getColliderBoneTargetPositionRotation(int contactBoneIndex,
out Vector3 targetPosition,
out Quaternion targetRotation)
{
using (new ProfilerSample("InteractionHand: getColliderBoneTargetPositionRotation"))
{
_handContactBoneMapFunctions[contactBoneIndex](_unwarpedHandData,
out targetPosition,
out targetRotation);
}
}
protected override bool initContact()
{
if (!isTracked) return false;
initContactBoneContainer();
initContactBones();
return true;
}
protected override void onPreEnableSoftContact()
{
resetContactBoneJoints();
}
protected override void onPostDisableSoftContact()
{
if (isTracked) resetContactBoneJoints();
}
#region Contact Bone Management
private void initContactBoneContainer()
{
if (_contactBoneParent != null)
{
Destroy(_contactBoneParent);
}
string name = (_unwarpedHandData.IsLeft ? "Left" : "Right") + " Interaction Hand Contact Bones";
_contactBoneParent = new GameObject(name);
}
private void initContactBones()
{
if (_contactBones != null)
{
for (int i = 0; i < _contactBones.Length; i++)
{
Destroy(_contactBones[i]);
}
}
_contactBones = new ContactBone[NUM_FINGERS * BONES_PER_FINGER + 1];
_handContactBoneMapFunctions = new BoneMapFunc[NUM_FINGERS * BONES_PER_FINGER + 1];
// Finger bones
for (int fingerIndex = 0; fingerIndex < NUM_FINGERS; fingerIndex++)
{
for (int jointIndex = 0; jointIndex < BONES_PER_FINGER; jointIndex++)
{
GameObject contactBoneObj = new GameObject("Contact Fingerbone", typeof(CapsuleCollider), typeof(Rigidbody), typeof(ContactBone));
contactBoneObj.layer = manager.contactBoneLayer;
Bone bone = _unwarpedHandData.Fingers[fingerIndex]
.Bone((Bone.BoneType)(jointIndex) + 1); // +1 to skip first bone.
int boneArrayIndex = fingerIndex * BONES_PER_FINGER + jointIndex;
contactBoneObj.transform.position = bone.Center.ToVector3();
contactBoneObj.transform.rotation = bone.Rotation.ToQuaternion();
// Remember the method we used to calculate this bone position from
// a Leap Hand for later.
int fingerIndexCopy = fingerIndex;
int jointIndexCopy = jointIndex;
_handContactBoneMapFunctions[boneArrayIndex] = (Leap.Hand hand,
out Vector3 targetPosition,
out Quaternion targetRotation) =>
{
Bone theBone = hand.Fingers[fingerIndexCopy].Bone((Bone.BoneType)(jointIndexCopy + 1));
targetPosition = theBone.Center.ToVector3();
targetRotation = theBone.Rotation.ToQuaternion();
};
CapsuleCollider capsule = contactBoneObj.GetComponent();
capsule.direction = 2;
capsule.radius = bone.Width * 0.5f;
capsule.height = bone.Length + bone.Width;
capsule.material = defaultContactBoneMaterial;
if (OnlyInitialiseIndexFinger)
{
capsule.enabled = (fingerIndex == 1 && (jointIndex == 2 || jointIndex == 1));
}
ContactBone contactBone = initContactBone(bone, contactBoneObj, boneArrayIndex, capsule);
contactBone.lastTargetPosition = bone.Center.ToVector3();
}
}
// Palm bone
{
// Palm is attached to the third metacarpal and derived from it.
GameObject contactBoneObj = new GameObject("Contact Palm Bone", typeof(BoxCollider), typeof(Rigidbody), typeof(ContactBone));
Bone bone = _unwarpedHandData.Fingers[(int)Finger.FingerType.TYPE_MIDDLE].Bone(Bone.BoneType.TYPE_METACARPAL);
int boneArrayIndex = NUM_FINGERS * BONES_PER_FINGER;
contactBoneObj.transform.position = _unwarpedHandData.PalmPosition.ToVector3();
contactBoneObj.transform.rotation = _unwarpedHandData.Rotation.ToQuaternion();
// Remember the method we used to calculate the palm from a Leap Hand for later.
_handContactBoneMapFunctions[boneArrayIndex] = (Leap.Hand hand,
out Vector3 targetPosition,
out Quaternion targetRotation) =>
{
targetPosition = hand.PalmPosition.ToVector3();
targetRotation = hand.Rotation.ToQuaternion();
};
BoxCollider box = contactBoneObj.GetComponent();
box.center = new Vector3(_unwarpedHandData.IsLeft ? -0.005f : 0.005f, bone.Width * -0.3f, -0.01f);
box.size = new Vector3(bone.Length, bone.Width, bone.Length);
box.material = defaultContactBoneMaterial;
if (OnlyInitialiseIndexFinger)
{
box.enabled = false;
}
initContactBone(null, contactBoneObj, boneArrayIndex, box);
}
// Constrain the bones to each other to prevent separation.
addContactBoneJoints();
}
private ContactBone initContactBone(Leap.Bone bone, GameObject contactBoneObj, int boneArrayIndex, Collider boneCollider)
{
contactBoneObj.layer = _contactBoneParent.gameObject.layer;
contactBoneObj.transform.localScale = Vector3.one;
ContactBone contactBone = contactBoneObj.GetComponent();
contactBone.collider = boneCollider;
contactBone.interactionController = this;
contactBone.interactionHand = this;
_contactBones[boneArrayIndex] = contactBone;
Transform capsuleTransform = contactBoneObj.transform;
capsuleTransform.SetParent(_contactBoneParent.transform, false);
Rigidbody body = contactBoneObj.GetComponent();
body.freezeRotation = true;
contactBone.rigidbody = body;
body.useGravity = false;
body.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic; // TODO: Allow different collision detection modes as an optimization.
body.mass = 0.1f;
body.position = bone != null ? bone.Center.ToVector3()
: _unwarpedHandData.PalmPosition.ToVector3();
body.rotation = bone != null ? bone.Rotation.ToQuaternion()
: _unwarpedHandData.Rotation.ToQuaternion();
contactBone.lastTargetPosition = bone != null ? bone.Center.ToVector3()
: _unwarpedHandData.PalmPosition.ToVector3();
return contactBone;
}
private void addContactBoneJoints()
{
for (int fingerIndex = 0; fingerIndex < NUM_FINGERS; fingerIndex++)
{
for (int jointIndex = 0; jointIndex < BONES_PER_FINGER; jointIndex++)
{
Bone bone = _unwarpedHandData.Fingers[fingerIndex].Bone((Bone.BoneType)(jointIndex) + 1); // +1 to skip first bone.
int boneArrayIndex = fingerIndex * BONES_PER_FINGER + jointIndex;
FixedJoint joint = _contactBones[boneArrayIndex].gameObject.AddComponent();
joint.autoConfigureConnectedAnchor = false;
if (jointIndex != 0)
{
Bone prevBone = _unwarpedHandData.Fingers[fingerIndex].Bone((Bone.BoneType)(jointIndex));
joint.connectedBody = _contactBones[boneArrayIndex - 1].rigidbody;
joint.anchor = Vector3.back * bone.Length / 2f;
joint.connectedAnchor = Vector3.forward * prevBone.Length / 2f;
_contactBones[boneArrayIndex].joint = joint;
}
else
{
joint.connectedBody = _contactBones[NUM_FINGERS * BONES_PER_FINGER].rigidbody;
joint.anchor = Vector3.back * bone.Length / 2f;
joint.connectedAnchor = _contactBones[NUM_FINGERS * BONES_PER_FINGER].transform.InverseTransformPoint(bone.PrevJoint.ToVector3());
_contactBones[boneArrayIndex].metacarpalJoint = joint;
}
}
}
}
/// Reconnects and resets all the joints in the hand.
private void resetContactBoneJoints()
{
// If contact bones array is null, there's nothing to reset. This can happen if
// the controller is disabled before it had a chance to initialize contact.
if (_contactBones == null) return;
// If the palm contact bone is null, we can't reset bone joints.
if (_contactBones[NUM_FINGERS * BONES_PER_FINGER] == null) return;
_contactBones[NUM_FINGERS * BONES_PER_FINGER].transform.position = _unwarpedHandData.PalmPosition.ToVector3();
_contactBones[NUM_FINGERS * BONES_PER_FINGER].transform.rotation = _unwarpedHandData.Rotation.ToQuaternion();
_contactBones[NUM_FINGERS * BONES_PER_FINGER].rigidbody.velocity = Vector3.zero;
_contactBones[NUM_FINGERS * BONES_PER_FINGER].rigidbody.angularVelocity = Vector3.zero;
for (int fingerIndex = 0; fingerIndex < NUM_FINGERS; fingerIndex++)
{
for (int jointIndex = 0; jointIndex < BONES_PER_FINGER; jointIndex++)
{
Bone bone = _unwarpedHandData.Fingers[fingerIndex].Bone((Bone.BoneType)(jointIndex) + 1); // +1 to skip first bone.
int boneArrayIndex = fingerIndex * BONES_PER_FINGER + jointIndex;
_contactBones[boneArrayIndex].transform.position = bone.Center.ToVector3();
_contactBones[boneArrayIndex].transform.rotation = bone.Rotation.ToQuaternion();
_contactBones[boneArrayIndex].rigidbody.position = bone.Center.ToVector3();
_contactBones[boneArrayIndex].rigidbody.rotation = bone.Rotation.ToQuaternion();
_contactBones[boneArrayIndex].rigidbody.velocity = Vector3.zero;
_contactBones[boneArrayIndex].rigidbody.angularVelocity = Vector3.zero;
if (jointIndex != 0 && _contactBones[boneArrayIndex].joint != null)
{
Bone prevBone = _unwarpedHandData.Fingers[fingerIndex].Bone((Bone.BoneType)(jointIndex));
_contactBones[boneArrayIndex].joint.connectedBody = _contactBones[boneArrayIndex - 1].rigidbody;
_contactBones[boneArrayIndex].joint.anchor = Vector3.back * bone.Length / 2f;
_contactBones[boneArrayIndex].joint.connectedAnchor = Vector3.forward * prevBone.Length / 2f;
}
else if (_contactBones[boneArrayIndex].metacarpalJoint != null)
{
_contactBones[boneArrayIndex].metacarpalJoint.connectedBody = _contactBones[NUM_FINGERS * BONES_PER_FINGER].rigidbody;
_contactBones[boneArrayIndex].metacarpalJoint.anchor = Vector3.back * bone.Length / 2f;
_contactBones[boneArrayIndex].metacarpalJoint.connectedAnchor = _contactBones[NUM_FINGERS * BONES_PER_FINGER].transform
.InverseTransformPoint(bone.PrevJoint.ToVector3());
}
}
}
}
///
/// A utility function that sets a Hand object's bones based on this InteractionHand.
/// Can be used to display a graphical hand that matches the physical one.
///
public void FillBones(Hand inHand)
{
if (softContactEnabled) { return; }
if (Application.isPlaying && _contactBones.Length == NUM_FINGERS * BONES_PER_FINGER + 1)
{
Vector elbowPos = inHand.Arm.ElbowPosition;
inHand.SetTransform(_contactBones[NUM_FINGERS * BONES_PER_FINGER].rigidbody.position, _contactBones[NUM_FINGERS * BONES_PER_FINGER].rigidbody.rotation);
for (int fingerIndex = 0; fingerIndex < NUM_FINGERS; fingerIndex++)
{
for (int jointIndex = 0; jointIndex < BONES_PER_FINGER; jointIndex++)
{
Bone bone = inHand.Fingers[fingerIndex].Bone((Bone.BoneType)(jointIndex) + 1);
int boneArrayIndex = fingerIndex * BONES_PER_FINGER + jointIndex;
Vector displacement = _contactBones[boneArrayIndex].rigidbody.position.ToVector() - bone.Center;
bone.Center += displacement;
bone.PrevJoint += displacement;
bone.NextJoint += displacement;
bone.Rotation = _contactBones[boneArrayIndex].rigidbody.rotation.ToLeapQuaternion();
}
}
inHand.Arm.PrevJoint = elbowPos;
inHand.Arm.Direction = (inHand.Arm.PrevJoint - inHand.Arm.NextJoint).Normalized;
inHand.Arm.Center = (inHand.Arm.PrevJoint + inHand.Arm.NextJoint) * 0.5f;
}
}
#endregion
#endregion
#region Grasp Controller Implementation
private List _graspManipulatorPoints = new List();
public override List graspManipulatorPoints
{
get
{
return _graspManipulatorPoints;
}
}
private void refreshGraspManipulatorPoints()
{
int bufferIndex = 0;
for (int i = 0; i < NUM_FINGERS; i++)
{
for (int boneIdx = 0; boneIdx < 2; boneIdx++)
{
// Update or add knuckle-joint and first-finger-bone positions as the grasp
// manipulator points for this Hand.
Vector3 point = leapHand.Fingers[i].bones[boneIdx].NextJoint.ToVector3();
if (_graspManipulatorPoints.Count - 1 < bufferIndex)
{
_graspManipulatorPoints.Add(point);
}
else
{
_graspManipulatorPoints[bufferIndex] = point;
}
bufferIndex += 1;
}
}
}
private HeuristicGrabClassifier _grabClassifier;
///
/// Handles logic determining whether a hand has grabbed or released an interaction object.
///
public HeuristicGrabClassifier grabClassifier
{
get
{
if (_grabClassifier == null) _grabClassifier = new HeuristicGrabClassifier(this);
return _grabClassifier;
}
}
private Vector3[] _fingertipPositionsBuffer = new Vector3[5];
///
/// Returns approximately where the controller is grasping the currently-grasped
/// InteractionBehaviour. Specifically, returns the average position of all grasping
/// fingertips of the InteractionHand.
///
/// This method will print an error if the hand is not currently grasping an object.
///
public override Vector3 GetGraspPoint()
{
if (!isGraspingObject)
{
Debug.LogError("Tried to get grasp point of InteractionHand, but it is not "
+ "currently grasping an object.", this);
return leapHand.PalmPosition.ToVector3();
}
int numGraspingFingertips = 0;
_grabClassifier.GetGraspingFingertipPositions(graspedObject, _fingertipPositionsBuffer, out numGraspingFingertips);
if (numGraspingFingertips > 0)
{
Vector3 sum = Vector3.zero;
for (int i = 0; i < numGraspingFingertips; i++)
{
sum += _fingertipPositionsBuffer[i];
}
return sum / numGraspingFingertips;
}
else
{
return leapHand.PalmPosition.ToVector3();
}
}
///
/// Attempts to manually initiate a grasp on the argument interaction object. A grasp
/// will only begin if a finger and thumb are both in contact with the interaction
/// object. If this method successfully initiates a grasp, it will return true,
/// otherwise it will return false.
///
protected override bool checkShouldGraspAtemporal(IInteractionBehaviour intObj)
{
if (grabClassifier.TryGrasp(intObj, leapHand))
{
var tempControllers = Pool>.Spawn();
try
{
tempControllers.Add(this);
intObj.BeginGrasp(tempControllers);
return true;
}
finally
{
tempControllers.Clear();
Pool>.Recycle(tempControllers);
}
}
return false;
}
public override void SwapGrasp(IInteractionBehaviour replacement)
{
var original = graspedObject;
base.SwapGrasp(replacement);
grabClassifier.SwapClassifierState(original, replacement);
}
protected override void fixedUpdateGraspingState()
{
//Feed Camera Transform in for Projective Grabbing Hack (details inside)
grabClassifier.FixedUpdateClassifierHandState(headTransform);
}
protected override void onGraspedObjectForciblyReleased(IInteractionBehaviour objectToBeReleased)
{
grabClassifier.NotifyGraspForciblyReleased(objectToBeReleased);
}
protected override bool checkShouldRelease(out IInteractionBehaviour objectToRelease)
{
return grabClassifier.FixedUpdateClassifierRelease(out objectToRelease);
}
protected override bool checkShouldGrasp(out IInteractionBehaviour objectToGrasp)
{
return grabClassifier.FixedUpdateClassifierGrasp(out objectToGrasp);
}
#endregion
#region Gizmos
#if UNITY_EDITOR
private Leap.Hand _testHand = null;
public override void OnDrawRuntimeGizmos(RuntimeGizmoDrawer drawer)
{
if (Application.isPlaying)
{
base.OnDrawRuntimeGizmos(drawer);
}
else
{
var provider = leapProvider;
if (provider == null)
{
provider = Hands.Provider;
}
if (_testHand == null && provider != null)
{
_testHand = provider.MakeTestHand(this.isLeft);
}
// Hover Point
_unwarpedHandData = _testHand; // hoverPoint is driven by this backing variable
drawHoverPoint(drawer, hoverPoint);
// Primary Hover Points
for (int i = 0; i < NUM_FINGERS; i++)
{
if (enabledPrimaryHoverFingertips[i])
{
drawPrimaryHoverPoint(drawer, _testHand.Fingers[i].TipPosition.ToVector3());
}
}
}
}
#endif
#endregion
}
}